/* Maxim DS3234 driver implementation */

/* Copyright (C) 2011 David Zanetti
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; version 2 of the License.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

/* This driver covers both the register access to the chip over SPI, and run local timers from the 32.768kHz output */
/* This effective implements the same features as the rtc driver but with the 2ppm DS3234 instead of a 20ppm crystal */

#include <avr/io.h>
#include <avr/pgmspace.h>
#include <stdio.h>
#include <string.h>
#include <avr/interrupt.h>
#include <util/atomic.h>

#include "global.h"
#include <util/delay.h>
#include <stdlib.h>

#include "serial.h"

#include "ds3234.h"
#include "gps.h"

/* interrupt to handle overflow of the timers into normal variables */
ISR(TCC1_OVF_vect) {
	ds3234_msb_s++;
	if (!ds3234_msb_s) {
		ds3234_era++;
	}
}

/* init the timers and chip */
void ds3234_init(void) {
	/* FIXME: set up SPI interface */

	/* reset MSB portion and era value */
	ds3234_msb_s = 0;
	ds3234_era = 0;

	/* - DS3234 32.768kHz output feeds a pin on the MCU.
	 * - We pipe that into the event system to increment a timer (0).
	 * - When timer 0 overflows, increment timer 1 via the event system.
	 * - When timer 1 overflows, fire interrupt to increment a global.
	 * - When ds3234_msb_s overflows, increment the era value
	 * - Both timers perform an input capture on another event channel (sourced from PPS GPS line)
	 *
	 * |           40 bits of seconds         | | 15 bits of fractions |
	 * |39|....|32|31|......|16|15|........|00|.|00|01|..........|13|14|
	 * |ds3234_era|ds3234_msb_s|     T1       | |          T0          |
	 *
	 */

	/* configure given port to have a pull-up, and positive edge detection */
	PORTC.DIRCLR = PIN0_bm; /* input */
	PORTC.PIN0CTRL = PORT_ISC_RISING_gc; /* rising edge */
	//PORTC.PIN0CTRL = PORT_ISC_RISING_gc | PORT_OPC_PULLUP_gc; /* rising edge */

	/* timer reset */
	TCC0.CNT = 0;
	TCC1.CNT = 0;

	/* interrupt to deal with TCC1 overflow */
	TCC1.INTCTRLA |= TC_OVFINTLVL_LO_gc; /* low level interrupt */

	/* enable low level interrupts, global still might be off */
	PMIC.CTRL |= PMIC_LOLVLEX_bm;

	/* timerc1 is fed from timerc0 overflows */
	EVSYS.CH2MUX = EVSYS_CHMUX_TCC0_OVF_gc; /* tcc0 ovf -> event channel 2 */
	TCC1.CTRLD = TC_EVSEL_CH0_gc | TC_EVACT_CAPT_gc | TC1_EVDLY_bm; /* listen for ch0 event, do input capture, 1 clock delay on capture triggering */
	TCC1.CTRLB |= TC1_CCAEN_bm; /* enable input capture A */
	TCC1.CTRLA = TC_CLKSEL_EVCH2_gc; /* clock us from events on channel 2 */
	/* timerc0 is fed from DS3234 clock input pin being triggered (high, because it's rising edge above */
	EVSYS.CH1MUX = EVSYS_CHMUX_PORTC_PIN0_gc; /* portc0 -> event channel 1 */
	TCC0.CTRLD = TC_EVACT_CAPT_gc | TC_EVSEL_CH0_gc; /* listen for ch0 event, do input capture */
	TCC0.CTRLB |= TC0_CCAEN_bm; /* enable input capture A */
	TCC0.PER = 32767; /* roll over at the top of 1s of ticks */
	TCC0.CTRLA = TC_CLKSEL_EVCH1_gc; /* clock us from events on channel 1 */


	return;
}

void ds3234_align(void) {
	uint32_t offset_s;
	uint16_t offset_ms; /* not really milliseconds */
	uint16_t timer_ms; /* not really milliseconds, timer captured value */
	uint16_t timer_s; /* timer captured lsb seconds */
	static uint16_t last_subsecond = 0xffff; /* invalid by default */

	/* save off the timer's capture, discarding sense bit */
	timer_ms = TCC0.CCA & 0x7fff;
	timer_s = TCC1.CCA;

#ifdef DEBUG_DS3234
	/* report current values */
	serial0_tx_PGM(PSTR("ds3234: "));
	//serial0_tx_dec(ds3234_msb_s);
	//serial0_tx_cout(' ');
	serial0_tx_dec(((uint32_t) ds3234_msb_s_pps << 16) | (uint32_t) timer_s);
	serial0_tx_cout(' ');
	serial0_tx_dec(timer_ms);
	serial0_tx_cout(' ');
	serial0_tx_dec(gps_s);
	serial0_tx_cr();

#endif

	/* compare the stored PPS time against the GPS time, and work out our difference */
	/* seconds difference, the partial seconds we'll infer by other means */
	/* -1 is there so we align to just behind it */
	offset_s = gps_s - (((uint32_t) ds3234_msb_s_pps << 16) | (uint32_t) timer_s);

#ifdef DEBUG_DS3234
	serial0_tx_PGM(PSTR("ds3234: "));
	serial0_tx_dec(offset_s);
	serial0_tx_PGM(PSTR("."));
	serial0_tx_dec(timer_ms);
	serial0_tx_PGM(PSTR(" required to align\r\n"));
#endif

	if (offset_s == 0) {
		/* we overshot, force it back a bit */
		ds3234_msb_s = (gps_s >> 16);
		TCC1.CNT = (gps_s & 0xffff) - 1;
		return;
	}

	/* if we have a substantial number of seconds to apply, then apply it directly */
	if (offset_s != 1) {
		ds3234_msb_s = (gps_s >> 16);
		TCC1.CNT = (gps_s & 0xffff); /* fixme: shouldn't this be -1? double buffering involved somewhere? */
		//TCC0.CNT = 0;
		return;
	}

	/* we should be only 1 second behind, less some change */
	/* goal is to get to very close to 32767 in TCC0.CNT, so slew */

	if (timer_ms < 32765) {
		if (timer_ms < 32300) {
			uint8_t n;
			for (n = 0; n < 200; n++) {
				EVSYS.STROBE = 0x2;
			}
		}
		EVSYS.STROBE = 0x2; /* channel 1, but slower */
	}

	return;

}
